package com.naiterui.ehp.bs.doctor.controller;

import com.naiterui.ehp.bp.bo.doctor.LoginParamBO;
import com.naiterui.ehp.bp.constants.CommonConstant;
import com.naiterui.ehp.bp.constants.SwaggerTag;
import com.naiterui.ehp.bp.domain.Doctor;
import com.naiterui.ehp.bp.domain.Login;
import com.naiterui.ehp.bp.support.exception.BusinessException;
import com.naiterui.ehp.bp.support.exception.PublicService;
import com.naiterui.ehp.bp.support.exception.ResponseVO;
import com.naiterui.ehp.bp.utils.biz.NameUtils;
import com.naiterui.ehp.bp.utils.encypt.DESPlus;
import com.naiterui.ehp.bp.utils.encypt.DESUtil;
import com.naiterui.ehp.bp.utils.encypt.RSAUtil;
import com.naiterui.ehp.bp.utils.lang.StringUtil;
import com.naiterui.ehp.bp.utils.qr.WeiXinEncrypUtil;
import com.naiterui.ehp.bs.doctor.exception.ExceptionCodes;
import com.naiterui.ehp.bs.doctor.service.IDoctorService;
import com.naiterui.ehp.bs.doctor.service.ILoginService;
import com.naiterui.ehp.bs.doctor.utils.LimitValueConfig;
import com.naiterui.ehp.bs.doctor.utils.PropertyValueConstants;
import com.naiterui.ehp.bs.doctor.utils.RedisUtils;
import com.naiterui.ehp.bs.doctor.vo.LoginVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import springfox.documentation.annotations.ApiIgnore;

import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * @version 2015年6月21日 上午11:36:54 by chenlin
 * @Description 登陆控制类
 */
@Controller
@RequestMapping(value = "ad/login")
@Api(tags = {SwaggerTag.DOCTOR + SwaggerTag.USER_ACCOUNT})
public class AdLoginController {

    private static final Logger LOGGER = LoggerFactory.getLogger(AdLoginController.class);

    @Autowired
    private ILoginService loginService;
    @Autowired
    private IDoctorService doctorService;

    /**
     * 验证手机号根式和非空
     * LoginController.validPhone()
     *
     * @param phoneNum
     *
     * @throws BusinessException
     * @Author chenlin
     * @Date 2016年3月25日
     * @since 1.0.0
     */
    private void validPhone(String phoneNum) throws BusinessException {
        if (phoneNum == null || "".equals(phoneNum)) {
            throw new BusinessException(ExceptionCodes.SMS_PHONE_NULL);
        }
        if (!StringUtil.isMobileNum(phoneNum)) {
            throw new BusinessException(ExceptionCodes.SMS_PHONE_ERROR);
        }
    }

    /**
     * 验证手机号获取验证码是否正确
     * LoginController.validCode()
     *
     * @param verifyCode
     * @param redisKey
     *
     * @throws BusinessException
     * @Author chenlin
     * @Date 2016年3月25日
     * @since 1.0.0
     */
    private void validCode(String verifyCode, String redisKey) throws BusinessException {
        if (verifyCode == null || "".equals(verifyCode)) {
            throw new BusinessException(ExceptionCodes.SMS_PHONE_VALID_NULL);
        }
        String cacheVerifyCode = (String) RedisUtils.get(redisKey);
        if (StringUtils.isEmpty(cacheVerifyCode)) {
            throw new BusinessException(ExceptionCodes.SMS_PHONE_VALID_ERROR);
        }
        if (!verifyCode.trim().equalsIgnoreCase(cacheVerifyCode)) {
            // 连续输入错误次数控制
            if (this.loginService.validUserOperator(redisKey)) {
                RedisUtils.delete(redisKey);
                throw new BusinessException(ExceptionCodes.SMS_PHONE_VALID_ERROR);
            }
            throw new BusinessException(ExceptionCodes.SMS_PHONE_VALID_ERROR);
        }
    }

    /**
     * 验证密码非空，解密，根式正确
     * LoginController.validPwd()
     *
     * @param password
     *
     * @throws BusinessException
     * @Author chenlin
     * @Date 2016年3月25日
     * @since 1.0.0
     */
    private String validPwd(String phoneNum, String password) throws BusinessException {

        // 1.密码非空判断
        if (StringUtils.isEmpty(password)) {
            LOGGER.error("密码空，phoneNum=" + phoneNum + ",password=" + password);
            throw new BusinessException(ExceptionCodes.REGIST_PWD_NULL);
        }

        // 2.密码解密
        String pwd;
        try {
            password = password.replaceAll(" ", "+");
            pwd = RSAUtil.decryptByPrivateKey(password);
        } catch (Exception e) {
            LOGGER.error("密码解密异常，phoneNum=" + phoneNum, e);
            throw new BusinessException(ExceptionCodes.FAILED);
        }

        // 3.解密后密码是否为空
        if (pwd == null || "".equals(pwd)) {
            throw new BusinessException(ExceptionCodes.REGIST_PWD_NULL);
        }

        pwd = pwd.trim();
        if (!LimitValueConfig.validateLimit(LimitValueConfig.LIMIT_PASSWORD, pwd)) {
            throw new BusinessException(ExceptionCodes.PASSWORD_UNQUALIFIED);
        }

        /*if (!ChackPasswordUtils.validatePassword(pwd)) {
            throw new BusinessException(ExceptionCodes.PASSWORD_UNQUALIFIED);
        }*/
        return pwd;
    }


    /**
     * 医生注册验证
     *
     * @param phoneNum   医生输入手机号码
     * @param verifyCode 验证码
     *
     * @return
     */
    @ApiOperation("医生注册短信验证")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "phoneNum", value = "手机号", dataType = "String", paramType = "form", required = true),
            @ApiImplicitParam(name = "verifyCode", value = "验证码", dataType = "String", paramType = "form", required = true),
    })
    @RequestMapping(value = "/validateRegist",method = {RequestMethod.GET, RequestMethod.POST})
    @ResponseBody
    public void validateRegist(String phoneNum, String verifyCode) throws BusinessException {

        // 1.判断手机号的非空和格式
        this.validPhone(phoneNum);
        // 2.判断短信验证码的非空和正确性
        String redisKey = PropertyValueConstants.PHONE_REGIST_KEY + phoneNum;
        this.validCode(verifyCode, redisKey);
        // 3. cache中记录验证成功，验证码存放10分钟
        String cacheKey = PropertyValueConstants.PHONE_VALID_REGIST_KEY + phoneNum;
        RedisUtils.set(cacheKey, "", PropertyValueConstants.SAVE_VERIFY_CODE);
    }

    /**
     * 医生注册
     *
     * @param phoneNum 手机号
     * @param password 公钥加密后的密码
     * @param invitationCode 邀请码（多个内含参数以"::"分隔）
     * @param regSource      0:渠道-非邀请注册,1:渠道-邀请注册,2:渠道-经纪人邀请注册
     *
     * @return
     */
    @ApiOperation("医生注册")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "phoneNum", value = "手机号", dataType = "String", paramType = "form", required = true),
            @ApiImplicitParam(name = "password", value = "公钥加密后的密码", dataType = "String", paramType = "form", required = true),
            @ApiImplicitParam(name = "invitationCode", value = "邀请码（多个内含参数以\"::\"分隔）", dataType = "String", paramType = "form"),
            @ApiImplicitParam(name = "regSource", value = "0:渠道-非邀请注册,1:渠道-邀请注册,2:渠道-经纪人邀请注册", dataType = "int", paramType = "form"),
            @ApiImplicitParam(name = "name", value = "医生姓名", dataType = "int", paramType = "form", required = true),

    })
    @RequestMapping(value = "/regist", method = {RequestMethod.GET, RequestMethod.POST})
    @ResponseBody
    public void regist(String phoneNum, String password, String invitationCode, Integer regSource, @RequestHeader String _p,
                       @RequestParam(required = false) String name) throws BusinessException {

        // 1.判断手机号的非空和格式
        this.validPhone(phoneNum);

        // 如果有则返回邀请参数
        String[] inviteParam = this.getInviteParam(phoneNum, regSource, invitationCode);


        // 2. 2.2.0 版本姓名格式要求 change by Gaoll 2016年3月7日
        boolean nameVerify = LimitValueConfig.validateLimit(LimitValueConfig.LIMIT_REALNAME, name);
        if (!nameVerify) {
            LOGGER.error(" doctor login regist ERROR ; name length not allowed : name {}", name);
            throw new BusinessException(ExceptionCodes.INVALID_CONTENT_LENGTH);
        }
        if (StringUtils.isNotEmpty(name) && !name.trim().matches(NameUtils.VALIDATE_REGEX_NAME)) {
            LOGGER.error(" doctor login regist ERROR ; name format not allowed : name {}", name);
            throw new BusinessException(ExceptionCodes.EMOJI_ERROR);
        }

        // 3.判断密码的非空和格式
        String pwd = this.validPwd(phoneNum, password);

        // 4.判断注册验证手机是否超时
        // 注册验证通过验证判断
        String cacheKey = PropertyValueConstants.PHONE_VALID_REGIST_KEY + phoneNum;
        if (!RedisUtils.existsKey(cacheKey)) {
            throw new BusinessException(ExceptionCodes.SMS_PHONE_VALID_FAIL);
        }

        // 6.系统注册判断
        boolean registH5Flag = false;
        // 关联id为0
        Long relationId = 0L;
        LoginParamBO loginParamBO = new LoginParamBO(phoneNum, name, pwd, LoginParamBO.RELATION_TYPE_PERSONAL, relationId, regSource,
                                                     registH5Flag);
        loginParamBO.setPlatform(_p);
        Long doctorId = this.loginService.saveLogin(loginParamBO);

        // 保存经纪人和医生关系，需要在getInviteId时做判断
        if (regSource == 2){
            loginService.saveAgentRelation(doctorId, inviteParam);
        }

        //给医生添加默认标签
        this.loginService.addDoctorLabel(loginParamBO, doctorId);
        // 7. 初始化医生信息
        this.loginService.initRegist(doctorId, phoneNum);
        RedisUtils.delete(PropertyValueConstants.PHONE_REGIST_KEY + phoneNum);
    }

    /**
     * 获取邀请ID
     * @param phoneNum
     * @param regSource
     * @param invitationCode
     * @return
     * @throws BusinessException
     */
    private String[] getInviteParam(String phoneNum, Integer regSource, String invitationCode) throws BusinessException {
        if (regSource == null) {
            LOGGER.error("注册渠道为空,phoneNum=" + phoneNum);
            throw new BusinessException(ExceptionCodes.REGIST_SOURCE_FAIL);
        }
        String[] inviteParam = null;
        if (regSource == 2){
            if(StringUtils.isEmpty(invitationCode)) {
                throw new BusinessException(ExceptionCodes.PARAM_ERROR, "邀请码为空");
            }
            try{
                invitationCode = invitationCode.replaceAll(" ", "+");
                String inviteStr = DESUtil.desDecrypt(CommonConstant.AGENT_INVITE_CUSTOM_DES_SECRET, invitationCode);
                inviteParam = inviteStr.split("::");
            }catch (Exception e){
                LOGGER.error("邀请码解密失败 phoneNum: {}, invitationCode: {}", phoneNum, invitationCode);
                throw new BusinessException(ExceptionCodes.PARAM_ERROR, "邀请码解密失败");
            }
            if (inviteParam.length != 3){
                throw new BusinessException(ExceptionCodes.PARAM_ERROR, "邀请码格式错误");
            }
        }
        return inviteParam;
    }


    /**
     * 扫码邀请注册
     *
     * @param phoneNum
     * @param password
     * @param regSource
     * @param _p
     * @param name
     * @param m
     * @param k
     *
     * @return
     */
    @PostMapping(value = "/qrcodeRegist")
    @ResponseBody
    @ApiOperation(value = "扫码邀请医生（目前弃用）")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "phoneNum", value = "手机号", required = true, paramType = "query", dataType = "string"),
            @ApiImplicitParam(name = "password", value = "密码", paramType = "true", dataType = "string"),
            @ApiImplicitParam(name = "regSource", value = "注册渠道", required = false, paramType = "query", dataType = "string"),
            @ApiImplicitParam(name = "name", value = "姓名", paramType = "query", dataType = "string"),
            @ApiImplicitParam(name = "m", value = "邀请加密串", paramType = "query", dataType = "string"),
            @ApiImplicitParam(name = "k", value = "邀请验证串", paramType = "query", dataType = "string"),
    })
    public ResponseVO<Void> wapRegist(String phoneNum, String password, Integer regSource, @RequestHeader String _p,
                                      @RequestParam(required = false) String name, String m, String k) {
        // 1.判断手机号的非空和格式
        try {
            this.validPhone(phoneNum);
        } catch (BusinessException e) {
            return PublicService.returnResponseVO(e.getCode());
        } catch (Exception e) {
            e.printStackTrace();
            LOGGER.error("注册医生失败,phoneNum=" + phoneNum);
            return PublicService.returnResponseVO(ExceptionCodes.FAILED);
        }

        // 2. 2.2.0 版本姓名格式要求 change by Gaoll 2016年3月7日
        boolean nameVerify = LimitValueConfig.validateLimit(LimitValueConfig.LIMIT_REALNAME, name);
        if (!nameVerify) {
            LOGGER.error(" doctor login regist ERROR ; name length not allowed : name {}", name);
            return PublicService.returnResponseVO(ExceptionCodes.INVALID_CONTENT_LENGTH);
        }
        if (StringUtils.isNotEmpty(name) && !name.trim().matches(NameUtils.VALIDATE_REGEX_NAME)) {
            LOGGER.error(" doctor login regist ERROR ; name format not allowed : name {}", name);
            return PublicService.returnResponseVO(ExceptionCodes.EMOJI_ERROR);
        }

        // 3.判断密码的非空和格式
        String pwd = null;
        try {
            pwd = this.validPwd(phoneNum, password);
        } catch (BusinessException e) {
            return PublicService.returnResponseVO(e.getCode());
        } catch (Exception e) {
            e.printStackTrace();
            LOGGER.error("注册医生失败,phoneNum=" + phoneNum);
            return PublicService.returnResponseVO(ExceptionCodes.FAILED);
        }

        // 4.判断注册验证手机是否超时
        try {
            // 注册验证通过验证判断
            String cacheKey = PropertyValueConstants.PHONE_VALID_REGIST_KEY + phoneNum;
            if (!RedisUtils.existsKey(cacheKey)) {
                return PublicService.returnResponseVO(ExceptionCodes.SMS_PHONE_VALID_FAIL);
            }
        } catch (Exception e) {
            e.printStackTrace();
            LOGGER.error("从redis获取注册医生验证码失败,phoneNum=" + phoneNum);
            return PublicService.returnResponseVO(ExceptionCodes.SMS_PHONE_VALID_FAIL);
        }

        // 5.如果请求参数含邀请码，判断邀请码的合法性，并找到关联关系
        if (regSource == null) {
            LOGGER.error("注册渠道为空,phoneNum=" + phoneNum);
            return PublicService.returnResponseVO(ExceptionCodes.REGIST_SOURCE_FAIL);
        }
        if (StringUtils.isEmpty(m) || StringUtils.isEmpty(k) || !WeiXinEncrypUtil.md5Valid(m, k)) {
            return PublicService.returnResponseVO(ExceptionCodes.REGIST_INVTER_M_ERROR);
        }
        try {
            // 6.系统注册判断
            boolean registH5Flag = true;
            // 关联id为0
            Long relationId = 0L;
            Long inviterId = Long.parseLong(WeiXinEncrypUtil.decryUrl(m).split(":")[0]);
            LoginParamBO loginParamBO = new LoginParamBO(phoneNum, name, pwd, LoginParamBO.RELATION_TYPE_DY, inviterId,
                                                         regSource,
                                                         registH5Flag);
            loginParamBO.setPlatform(_p);
            Long doctorId = this.loginService.saveLogin(loginParamBO);

            //给医生添加默认标签
            this.loginService.addDoctorLabel(loginParamBO, doctorId);
            // 7. 初始化医生信息
            this.loginService.initRegist(doctorId, phoneNum);
        } catch (BusinessException e) {
            return PublicService.returnResponseVO(e.getCode());
        } catch (Exception e) {
            e.printStackTrace();
            LOGGER.error("注册医生失败,phoneNum=" + phoneNum);
            return PublicService.returnResponseVO(ExceptionCodes.FAILED);
        }
        RedisUtils.delete(PropertyValueConstants.PHONE_REGIST_KEY + phoneNum);
        return PublicService.returnResponseVO(ExceptionCodes.SUCCESS);
    }


    @ApiOperation("获取登录密码加密公钥")
    @RequestMapping(value = "/getPublicKey", method = {RequestMethod.GET, RequestMethod.POST})
    @ResponseBody
    public Map<String, String> getRSAPublicKey() throws Exception {
        String key = RSAUtil.getPublicKeyStr();
        Map<String, String> map = new HashMap<>();
        map.put("publicKey", key);

        return map;
    }

    /**
     * 登陆获取loginKey
     *
     * @param phoneNum
     *
     * @return
     */
    @ApiOperation("根据手机号登录生成loginKey")
    @RequestMapping(value = "/genLoginKey", method = {RequestMethod.GET, RequestMethod.POST})
    @ResponseBody
    public Map<String, String> genLoginKey(String phoneNum) throws BusinessException {
        // 1.判断手机号是否合法
        if (phoneNum == null || "".equals(phoneNum)) {
            throw new BusinessException(ExceptionCodes.SMS_PHONE_NULL);
        }
        if (!StringUtil.isMobileNum(phoneNum)) {
            throw new BusinessException(ExceptionCodes.SMS_PHONE_ERROR);
        }

        // 2.根据手机查询用户信息
        Login login = this.loginService.findLoginByPhoneNum(phoneNum);
        if (login == null) {
            throw new BusinessException(ExceptionCodes.LOGIN_NOT_EXSIT);
        }

        // 3.获取用户id和盐
        long loginId = login.getId();
        String salt = login.getSalt();
        Map<String, String> map = new HashMap<>();
        map.put("loginId", String.valueOf(loginId));
        String loginKey = DESPlus.gengerateLoginKey(loginId, salt);
        map.put("loginKey", loginKey);

        String cacheKey = PropertyValueConstants.DOCTOR_LOGIN_KEY + String.valueOf(loginId);
        RedisUtils.set(cacheKey, loginKey, PropertyValueConstants.SAVE_VERIFY_CODE);
        return map;
    }

    /**
     * 用户登录
     */
    @ResponseBody
    @RequestMapping
    @ApiOperation(value = "账号密码登录", httpMethod = "POST", produces = MediaType.APPLICATION_JSON_VALUE)
    @ApiImplicitParams({@ApiImplicitParam(name = "loginId", value = "医生id", required = true, dataType = "long", paramType = "query"),
            @ApiImplicitParam(name = "loginKey", value = "登录key", required = true, dataType = "String", paramType = "query"),
            @ApiImplicitParam(name = "password", value = "医生登陆密码", required = true, dataType = "String", paramType = "query"),
            @ApiImplicitParam(name = "deviceSN", value = "设备sn", required = true, dataType = "String", paramType = "query")})
    public ResponseVO<LoginVO> login(@RequestParam(value = "loginId", required = false) Long loginId, String loginKey, String password,
                                     String deviceSN, @RequestHeader String _p, @RequestHeader String _v,
                                     HttpServletRequest request) throws BusinessException {
        LOGGER.info("用户登陆接口 请求参数：loginId = [{}], loginKey = [{}], password = [{}], deviceSN = [{}], _p = [{}], _v = [{}]", loginId, loginKey,
                    password, deviceSN, _p, _v);

        // 1.登录参数校验
        if (loginId == null || loginKey == null) {
            LOGGER.info("参数异常，医生id:{},loginKey:{}", loginId, loginKey);
            throw new BusinessException(ExceptionCodes.PARAM_ERROR);
        }

        if (StringUtils.isEmpty(password)) {
            LOGGER.info("密码空，医生id:{}，password:{}", loginId, password);
            throw new BusinessException(ExceptionCodes.REGIST_PWD_NULL);
        }
        try {
            password = password.replaceAll(" ", "+");
            password = RSAUtil.decryptByPrivateKey(password);
        } catch (Exception e) {
            LOGGER.error("密码解密异常，loginId={}", loginId, e);
            throw new BusinessException(ExceptionCodes.FAILED);
        }

        if (StringUtils.isEmpty(password)) {
            throw new BusinessException(ExceptionCodes.REGIST_PWD_NULL);
        }
        password = password.trim();

        // 2.根据loginId判断用户信息，并返回登录信息
        LoginVO loginVO = this.loginService.saveLoginInfoById(loginId, loginKey, password, _v, _p, deviceSN, request);

        return PublicService.returnResponseVO(loginVO);
    }

    /**
     * 短信验证码验证登录
     * LoginController.validPhoneCodeLogin()
     *
     * @param phoneNum
     * @param verifyCode
     * @param deviceSN
     * @param _p
     * @param _v
     * @param request
     *
     * @return
     *
     * @Author chenlin
     * @Date 2016年3月28日
     * @since 1.0.0
     */
    @ApiOperation("短信验证码登录")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "phoneNum", value = "手机号", dataType = "String", paramType = "form", required = true),
            @ApiImplicitParam(name = "verifyCode", value = "验证码", dataType = "String", paramType = "form", required = true),
            @ApiImplicitParam(name = "deviceSN", value = "设备编号", dataType = "String", paramType = "form", required = true),

    })
    @RequestMapping(value = "validLogin", method = {RequestMethod.GET, RequestMethod.POST})
    @ResponseBody
    public ResponseVO<LoginVO> validPhoneCodeLogin(String phoneNum, String verifyCode, String deviceSN, @RequestHeader String _p,
        @RequestHeader String _v, HttpServletRequest request) throws BusinessException {

        // 1.判断手机号是否合法
        this.validPhone(phoneNum);

        // 2.判断短信验证码的非空和正确性
        String redisKey = PropertyValueConstants.PHONE_LOGIN_PWD_KEY + phoneNum;
        this.validCode(verifyCode, redisKey);

        // 3.判断医生是否注册，注册直接登录，未注册进行注册。
        LoginVO loginVO = this.loginService.saveLoginInfoByPhone(phoneNum, _v, _p, deviceSN, request);
        RedisUtils.delete(redisKey);
        return PublicService.returnResponseVO(loginVO);
    }

    /**
     * 密码 + 短信验证码验证登录
     * LoginController.combineLogin()
     */
    @ApiOperation("账号密码+短信验证码组合登录")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "loginId", value = "loginId", dataType = "String", paramType = "form", required = true),
            @ApiImplicitParam(name = "loginKey", value = "loginKey", dataType = "String", paramType = "form", required = true),
            @ApiImplicitParam(name = "password", value = "密文密码", dataType = "String", paramType = "form", required = true),
            @ApiImplicitParam(name = "phoneNum", value = "手机号", dataType = "String", paramType = "form", required = true),
            @ApiImplicitParam(name = "verifyCode", value = "验证码", dataType = "String", paramType = "form", required = true),
            @ApiImplicitParam(name = "deviceSN", value = "设备编号", dataType = "String", paramType = "form", required = true),
    })
    @PostMapping("combine")
    @ResponseBody
    public ResponseVO<LoginVO> combineLogin(
        @RequestParam(value = "loginId", required = false) Long loginId,
        String loginKey,
        String password,
        String deviceSN,
        String phoneNum,
        String verifyCode,
        @RequestHeader String _p,
        @RequestHeader String _v,
        HttpServletRequest request) throws BusinessException {

        // 1.登录参数校验
        if (loginId == null || loginKey == null) {
            LOGGER.info("参数异常，医生id:{},loginKey:{}", loginId, loginKey);
            throw new BusinessException(ExceptionCodes.PARAM_ERROR);
        }
        if (StringUtils.isEmpty(password)) {
            LOGGER.info("密码空，医生id:{}，password:{}", loginId, password);
            throw new BusinessException(ExceptionCodes.REGIST_PWD_NULL);
        }
        try {
            password = password.replaceAll(" ", "+");
            password = RSAUtil.decryptByPrivateKey(password);
        } catch (Exception e) {
            LOGGER.error("密码解密异常，loginId={}", loginId, e);
            throw new BusinessException(ExceptionCodes.FAILED);
        }
        if (StringUtils.isEmpty(password)) {
            throw new BusinessException(ExceptionCodes.REGIST_PWD_NULL);
        }
        // 判断手机号是否合法
        this.validPhone(phoneNum);
        // 判断短信验证码的非空和正确性
        String redisKey = PropertyValueConstants.PHONE_LOGIN_PWD_KEY + phoneNum;
        this.validCode(verifyCode, redisKey);
        password = password.trim();
        // 账号密码 + 验证码 判断医生是否注册，注册直接登录，未注册进行注册。
        LoginVO loginVO = this.loginService.saveLoginInfoConbine(loginId, loginKey, password,phoneNum, _v, _p, deviceSN, request);
        RedisUtils.delete(redisKey);
        return PublicService.returnResponseVO(loginVO);
    }


    /**
     * 忘记密码，验证手机
     *
     * @param phoneNum
     * @param verifyCode
     *
     * @return
     */
    @ApiOperation("忘记密码-校验验证码(未登录情况)")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "phoneNum", value = "手机号", dataType = "String", paramType = "form", required = true),
            @ApiImplicitParam(name = "verifyCode", value = "验证码", dataType = "String", paramType = "form", required = true),
    })
    @RequestMapping(value = "/validateForgetPwd", method = {RequestMethod.GET, RequestMethod.POST})
    @ResponseBody
    public void validateForgetPwd(String phoneNum, String verifyCode) throws BusinessException {
        if (phoneNum == null || "".equals(phoneNum)) {
            throw new BusinessException(ExceptionCodes.SMS_PHONE_NULL);
        }
        if (!StringUtil.isMobileNum(phoneNum)) {
            throw new BusinessException(ExceptionCodes.SMS_PHONE_ERROR);
        }

        // if (verifyCode == null || "".equals(verifyCode)) {
        // return PublicService.returnValue(ExceptionCodes.SMS_PHONE_VALID_NULL);
        // }
        // try {
        // String cacheVerifyCode = (String) RedisUtils.get(PropertyValueConstants.PHONE_FORGET_KEY
        // + phoneNum);
        // if (!verifyCode.trim().equalsIgnoreCase(cacheVerifyCode)) {
        // return PublicService.returnValue(ExceptionCodes.SMS_PHONE_VALID_ERROR);
        // }
        // } catch (Exception e) {
        // e.printStackTrace();
        // LOGGER.error("忘记密码验证码获取失败");
        // return PublicService.returnValue(ExceptionCodes.FAILED);
        // }
        String redisKey = PropertyValueConstants.PHONE_FORGET_KEY + phoneNum;
        this.validCode(verifyCode, redisKey);
        // 3.电话是已经存在
        if (!this.loginService.findCountByPhoneNum(phoneNum)) {
            throw new BusinessException(ExceptionCodes.LOGIN_NOT_EXSIT);
        }

        // cache中记录验证成功，验证码存放10分钟
        String cacheKey = PropertyValueConstants.PHONE_VALID_FORGET_KEY + phoneNum;
        RedisUtils.set(cacheKey, "", PropertyValueConstants.SAVE_VERIFY_CODE);

    }

    /**
     * 忘记密码设置手机
     *
     * @param phoneNum
     * @param password
     *
     * @return
     */
    @ApiOperation("忘记密码-重置密码(未登录情况)")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "phoneNum", value = "手机号", dataType = "String", paramType = "form", required = true),
            @ApiImplicitParam(name = "password", value = "密文密码", dataType = "String", paramType = "form", required = true),
    })
    @RequestMapping(value = "/setForgetPwd", method = {RequestMethod.GET, RequestMethod.POST})
    @ResponseBody
    public void setForgetPwd(String phoneNum, String password) throws BusinessException {

        // 1.电话是否正确
        if (phoneNum == null || "".equals(phoneNum)) {
            throw new BusinessException(ExceptionCodes.SMS_PHONE_NULL);
        }
        if (!StringUtil.isMobileNum(phoneNum)) {
            throw new BusinessException(ExceptionCodes.SMS_PHONE_ERROR);
        }

        password = this.validPwd(phoneNum, password);

        // 4.根据手机查询用户信息
        Login login = this.loginService.findLoginByPhoneNum(phoneNum);

        if (login == null) {
            throw new BusinessException(ExceptionCodes.REGIST_EXSIT);
        }

        // 5.判断忘记密码验证手机是否超时
        String cacheKey = PropertyValueConstants.PHONE_VALID_FORGET_KEY + phoneNum;
        if (!RedisUtils.existsKey(cacheKey)) {
            throw new BusinessException(ExceptionCodes.SMS_PHONE_VALID_FAIL);
        }

        // 6.保存密码
        Doctor doctor = this.doctorService.findDoctorByUserId(login.getId());
        this.loginService.savePassword(doctor.getId(), login, password);
        RedisUtils.delete(PropertyValueConstants.PHONE_FORGET_KEY + phoneNum);
        RedisUtils.delete(cacheKey);
    }

    /**
     * 登陆重置密码
     *
     * @param doctorId
     * @param phoneNum
     * @param verifyCode
     * @param newPwd
     *
     * @return
     */
    @ApiOperation("修改密码-验证码改密(已登录)")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "doctorId", value = "医生ID", dataType = "long", paramType = "form", required = true),
            @ApiImplicitParam(name = "phoneNum", value = "手机号", dataType = "String", paramType = "form", required = true),
            @ApiImplicitParam(name = "verifyCode", value = "验证码", dataType = "String", paramType = "form", required = true),
            @ApiImplicitParam(name = "newPwd", value = "密文新密码", dataType = "String", paramType = "form", required = true),
    })
    @RequestMapping(value = "/setDoctorPwd", method = {RequestMethod.GET, RequestMethod.POST})
    @ResponseBody
    public void setDoctorPwd(Long doctorId, String phoneNum, String verifyCode, String newPwd) throws Exception {

        if (doctorId == null) {
            LOGGER.info("参数异常，医生id:" + doctorId);
            throw new BusinessException(ExceptionCodes.PARAM_ERROR);
        }

        // 1.密码解密
        newPwd = newPwd.replaceAll(" ", "+");
        newPwd = RSAUtil.decryptByPrivateKey(newPwd);

        // 2.密码是否符合规则
        if ("".equals(newPwd)) {
            throw new BusinessException(ExceptionCodes.REGIST_PWD_NULL);
        }

        newPwd = newPwd.trim();
        if (!LimitValueConfig.validateLimit(LimitValueConfig.LIMIT_PASSWORD, newPwd)) {
            throw new BusinessException(ExceptionCodes.REGIST_PWD_ERROR);
        }
        /*if (!ChackPasswordUtils.validatePassword(newPwd)) {
            throw new BusinessException(ExceptionCodes.PASSWORD_UNQUALIFIED);
        }*/

        // 3.验证手机验证码
        // if (verifyCode == null || "".equals(verifyCode)) {
        // return PublicService.returnValue(ExceptionCodes.SMS_PHONE_VALID_NULL);
        // }
        //
        // String cacheVerifyCode = (String)
        // RedisUtils.get(PropertyValueConstants.PHONE_MODIFY_PWD_KEY + phoneNum);
        // if (!verifyCode.trim().equalsIgnoreCase(cacheVerifyCode)) {
        // return PublicService.returnValue(ExceptionCodes.SMS_PHONE_VALID_ERROR);
        // }
        String redisKey = PropertyValueConstants.PHONE_MODIFY_PWD_KEY + phoneNum;
        this.validCode(verifyCode, redisKey);

        // 4.根据用户id，查询用户信息
        Doctor doctor = this.doctorService.getDoctorById(doctorId);

        if (doctor == null) {
            throw new BusinessException(ExceptionCodes.FAILED);
        }

        // 5.获取登陆信息
        Login login = this.loginService.findLoginById(doctor.getUserId());

        if (login == null) {
            throw new BusinessException(ExceptionCodes.REGIST_EXSIT);
        }

        // 6.验证手机
        if (phoneNum == null || "".equals(phoneNum)) {
            throw new BusinessException(ExceptionCodes.SMS_PHONE_NULL);
        }
        String srcPhone = login.getPhoneNum();
        if (!phoneNum.equals(srcPhone)) {
            throw new BusinessException(ExceptionCodes.LOGIN_PWD_PHONE_FAIL);
        }

        // 7.保存密码
        this.loginService.savePassword(doctorId, login, newPwd);
        RedisUtils.delete(PropertyValueConstants.PHONE_REGIST_KEY + phoneNum);
        RedisUtils.delete(redisKey);
    }

    /**
     * <登出，清除token信息>
     */
    @ApiOperation(value = "登出",notes = "清除token信息")
    @RequestMapping(value = "/logout", method = {RequestMethod.GET, RequestMethod.POST})
    @ResponseBody
    public void logout(Long doctorId, String token) throws BusinessException {

        // 1.根据医生id，查询登陆id
        Doctor doctor = this.doctorService.getDoctorById(doctorId);

        if (doctor == null) {
            LOGGER.warn("医生信息不存在！doctorId:{}", doctorId);
            throw new BusinessException(ExceptionCodes.USER_NOT_EXIST);
        }
        // 2.获取登陆信息，退出
        Login login = this.loginService.findLoginById(doctor.getUserId());

        String dbToken = login.getToken();
        if (dbToken == null || "".equals(dbToken)) {
            return;
        }

        // 3.验证id和token
        if (!dbToken.equals(token)) {
            throw new BusinessException(ExceptionCodes.FAILED);
        }

        // 4.清除数据库中token
        login.setToken(null);
        login.setChangedAt(new Date(System.currentTimeMillis()));
        this.loginService.updateLogin(login);
        // 登出日志
        this.loginService.saveLogoutLog(login, doctor);

        // 清理医生登录TOKEN
        String cacheKey = PropertyValueConstants.DOCTOR_LOGIN_TOKEN_KEY + doctorId;
        RedisUtils.delete(cacheKey);
    }

    /**
     * 向redis中设置验证限制规则
     *
     * @param sign        操作sign
     * @param validType   验证类型:1-自然日;2-指定时间
     * @param validPeriod 验证周期(单位秒)
     *
     * @return
     */
    @RequestMapping(value = "setValidExp")
    @ResponseBody
    @ApiIgnore
    // TODO 待确认干嘛用的
    public Map<String, String> setValidExp(String sign, Long validType, Long validCount, Long validPeriod) throws BusinessException {

        if (StringUtils.isEmpty(sign) || validType <= 0 || validCount <= 0) {
            LOGGER.info("set validExp param is valid, sign {} validType {} validCount {} ", sign, validType, validCount);
            throw new BusinessException(ExceptionCodes.PARAM_ERROR);
        }
        if (!"poiuytrewq098765421".equals(sign)) {
            LOGGER.info("set validExp is valid,sign {} ", sign);
            throw new BusinessException(ExceptionCodes.PARAM_ERROR);
        }
        if (2L == validType && validPeriod <= 0) {
            LOGGER.info("set validExp is valid, validPeriod {} ", validPeriod);
            throw new BusinessException(ExceptionCodes.PARAM_ERROR);
        }
        this.saveValidExp(validType, validCount, validPeriod);
        return this.getValidExp();
    }

    /**
     * 删除用户redis中验证数据
     *
     * @param sign     操作sign
     * @param phoneNum 医生手机号
     * @param actionType 医生手机号
     *
     * @return
     */
    @RequestMapping("delDoctorValid")
    @ResponseBody
    @ApiIgnore
    // TODO 待确认干嘛用的
    public void delDoctorValid(String sign, String phoneNum, Integer actionType) throws BusinessException {

        if (StringUtils.isEmpty(sign) || !"1qaz2wsxcde3".equals(sign)) {
            LOGGER.info("delDoctorValid param is not right, sign {} ", sign);
            throw new BusinessException(ExceptionCodes.PARAM_ERROR);
        }
        String cacheKey = this.getDelDoctorValidKey(phoneNum, actionType);
        RedisUtils.delete(PropertyValueConstants.getSmsValidUserOperatorKey(cacheKey));
    }

    public String getDelDoctorValidKey(String phoneNum, Integer actionType) throws BusinessException {
        String cacheKey = "";
        switch (actionType) {
            case 1:
                cacheKey = PropertyValueConstants.PHONE_REGIST_KEY + phoneNum;
                break;
            case 2:
                cacheKey = PropertyValueConstants.PHONE_FORGET_KEY + phoneNum;
                break;
            case 3:
                cacheKey = PropertyValueConstants.PHONE_MODIFY_PHONE_KEY + phoneNum;
                break;
            case 4:
                cacheKey = PropertyValueConstants.PHONE_MODIFY_PWD_KEY + phoneNum;
                break;
                //TODO 5\6都是登录验证码 ？
            case 5:
                cacheKey = PropertyValueConstants.PHONE_LOGIN_PWD_KEY + phoneNum;
                break;
            case 6:
                cacheKey = PropertyValueConstants.PHONE_LOGIN_PWD_KEY + phoneNum;
                break;
            default:
                LOGGER.error(" {}发送短信验证码不正确", actionType);
                throw new BusinessException(ExceptionCodes.SMS_PHONE_ACT_ERROR);
        }
        return cacheKey;
    }

    /**
     * 查询redis中设置验证限制规则
     *
     * @return
     */
    @RequestMapping("findValidExp")
    @ResponseBody
    @ApiIgnore
    // TODO 待确认干嘛用的
    public Map<String, String> findValidExp() {
        return this.getValidExp();
    }

    private void saveValidExp(Long validType, Long validCount, Long validPeriod) {
        // 采用默认自然日方式
        if (1 == validType) {
            RedisUtils.hdel(PropertyValueConstants.VALID_KEY, PropertyValueConstants.VALID_TIME_FIELD);
        } else if (2 == validType) {
            RedisUtils.hset(PropertyValueConstants.VALID_KEY, PropertyValueConstants.VALID_TIME_FIELD, String.valueOf(validPeriod));
        } else {
            RedisUtils.hdel(PropertyValueConstants.VALID_KEY, PropertyValueConstants.VALID_TIME_FIELD);
        }
        RedisUtils.hset(PropertyValueConstants.VALID_KEY, PropertyValueConstants.VALID_COUNT_FIELD, String.valueOf(validCount));
    }

    /**
     * 快捷登录
     */
    @ResponseBody
    @PostMapping("/fast")
    @ApiOperation("快捷登录")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "loginId", value = "医生id", required = true, dataType = "long", paramType = "query"),
            @ApiImplicitParam(name = "loginKey", value = "登录key", required = true, dataType = "String", paramType = "query"),
            @ApiImplicitParam(name = "fastLoginToken", value = "快速登录token", required = true, dataType = "String", paramType = "query"),
            @ApiImplicitParam(name = "deviceSN", value = "设备sn", required = true, dataType = "String", paramType = "query")
    })
    public ResponseVO<LoginVO> fast(@RequestParam(value = "loginId", required = false) Long loginId, String loginKey, String fastLoginToken,
                                     String deviceSN, @RequestHeader String _p, @RequestHeader String _v,
                                     HttpServletRequest request) throws BusinessException {
        LOGGER.info("用户快捷登录接口 请求参数：loginId: {}, loginKey: {}, fastLoginToken: {}, deviceSN: {}, _p: {}, _v: {}", loginId, loginKey, fastLoginToken, deviceSN, _p, _v);
        // 1.登录参数校验
        if (loginId == null || loginKey == null||deviceSN ==null) {
            LOGGER.info("参数异常，医生id: {}, loginKey: {}, deviceSN: {}", loginId, loginKey, deviceSN);
            throw new BusinessException(ExceptionCodes.PARAM_ERROR);
        }

        if (StringUtils.isEmpty(fastLoginToken)) {
            throw new BusinessException(ExceptionCodes.REGIST_PWD_NULL);
        }
        fastLoginToken = fastLoginToken.trim();

        // 2.根据loginId判断用户信息，并返回登录信息
        LoginVO loginVO = this.loginService.fastLoginInfoById(loginId, loginKey, fastLoginToken, _v, _p, deviceSN, request);

        return PublicService.returnResponseVO(loginVO);
    }

    private Map<String, String> getValidExp() {
        return RedisUtils.hgetAll(PropertyValueConstants.VALID_KEY);
    }
}
